<template>
  <div class="my-process-designer">
    <div class="my-process-designer__header">
      <template v-if="!$slots['control-header']">
        <!--<el-button-group key="align-control">
          <el-tooltip effect="light" content="向左对齐">
            <el-button size="small" class="align align-left" icon="el-icon-s-data" @click="elementsAlign('left')" />
          </el-tooltip>
          <el-tooltip effect="light" content="向右对齐">
            <el-button size="small" class="align align-right" icon="el-icon-s-data" @click="elementsAlign('right')" />
          </el-tooltip>
          <el-tooltip effect="light" content="向上对齐">
            <el-button size="small" class="align align-top" icon="el-icon-s-data" @click="elementsAlign('top')" />
          </el-tooltip>
          <el-tooltip effect="light" content="向下对齐">
            <el-button size="small" class="align align-bottom" icon="el-icon-s-data" @click="elementsAlign('bottom')" />
          </el-tooltip>
          <el-tooltip effect="light" content="水平居中">
            <el-button size="small" class="align align-center" icon="el-icon-s-data" @click="elementsAlign('center')" />
          </el-tooltip>
          <el-tooltip effect="light" content="垂直居中">
            <el-button size="small" class="align align-middle" icon="el-icon-s-data" @click="elementsAlign('middle')" />
          </el-tooltip>
        </el-button-group>-->
        <el-button-group key="scale-control">
          <el-tooltip effect="light" content="缩小视图">
            <el-button size="small" :disabled="defaultZoom < 0.2" icon="el-icon-zoom-out" @click="processZoomOut()" />
          </el-tooltip>
          <el-button size="small">{{ Math.floor(defaultZoom * 10 * 10) + "%" }}</el-button>
          <el-tooltip effect="light" content="放大视图">
            <el-button size="small" :disabled="defaultZoom > 4" icon="el-icon-zoom-in" @click="processZoomIn()" />
          </el-tooltip>
          <el-tooltip effect="light" content="重置视图并居中">
            <el-button size="small" icon="el-icon-c-scale-to-original" @click="processReZoom()" />
          </el-tooltip>
        </el-button-group>
        <el-button-group key="stack-control">
          <el-tooltip effect="light" content="撤销">
            <el-button size="small" :disabled="!revocable" icon="el-icon-refresh-left" @click="processUndo()" />
          </el-tooltip>
          <el-tooltip effect="light" content="恢复">
            <el-button size="small" :disabled="!recoverable" icon="el-icon-refresh-right" @click="processRedo()" />
          </el-tooltip>
          <el-tooltip effect="light" content="重新绘制">
            <el-button size="small" icon="el-icon-refresh" @click="processRestart" />
          </el-tooltip>
        </el-button-group>
        <el-button-group>
          <el-button size="small" icon="el-icon-view" @click="previewProcessXML">预览</el-button>
          <el-button size="small" icon="el-icon-edit" @click="saveProcess">保存</el-button>
        </el-button-group>
      </template>
    </div>
    <div class="my-process-designer__container">
      <div ref="bpmn-canvas" class="my-process-designer__canvas" />
    </div>
    <el-dialog title="预览" width="80%" :visible.sync="previewVisible" destroy-on-close>
      <lab-code v-model="previewData" :config="{ language: 'xml'}" style="height: 600px" />
    </el-dialog>
  </div>
</template>
/**
* @description
* @author mao
* @time: 2021/8/12 10:29 上午
**/
<script>
// 设计器
import BpmnModeler from 'bpmn-js/lib/Modeler'
// 翻译
import customTranslate from './plugins/translate/customTranslate'
import translationsCN from './plugins/translate/zh'
// 左侧菜单
import CustomPaletteProvider from './plugins/palette'
// 自定义元素选中时的弹出菜单（修改 默认任务 为 用户任务）
import CustomContentPadProvider from './plugins/content-pad'
// 描述扩
import flowableModdleDescriptor from './plugins/descriptor/flowableDescriptor.json'
// 解析扩展
import flowableModdleExtension from './plugins/extension-moddle/flowable'
// 默认模板
import DefaultEmptyXML from './plugins/defaultEmpty'
export default {
  name: 'BpmnDesign',
  model: {
    prop: 'value',
    event: 'input'
  },
  props: {
    value: {
      type: String,
      default: ''
    },
    events: {
      type: Array,
      default: () => ['element.click']
    }
  },
  inject: {
    bpmn: {
      from: 'bpmn'
    },
    loadProcess: {
      from: 'loadProcess'
    },
    saveXml: {
      from: 'saveXml'
    }
  },
  data() {
    return {
      bpmnModeler: undefined,
      recoverable: false,
      revocable: false,
      defaultZoom: 1,
      previewData: '',
      previewVisible: false,
      process: {}
    }
  },
  computed: {
    addModules() {
      const modules = []
      // 左侧菜单
      modules.push(CustomPaletteProvider)
      modules.push(CustomContentPadProvider)
      // 翻译模块
      const TranslateModule = {
        translate: ['value', customTranslate(translationsCN)]
      }
      modules.push(TranslateModule)
      // 扩展属性
      modules.push(flowableModdleExtension)
      return modules
    },
    addExtensions() {
      const extensions = {}
      extensions.flowable = flowableModdleDescriptor
      return extensions
    }
  },
  watch: {
    previewVisible() {
      if (!this.previewVisible) {
        const { processDefId } = this.$route.query
        this.createDiagram(this.previewData, processDefId)
      }
    }
  },
  mounted() {
    this.initModeler()
    this.loadProcess(this.createDiagram)
  },
  methods: {
    initModeler() {
      if (this.bpmnModeler) {
        return
      }
      this.bpmnModeler = new BpmnModeler({
        container: this.$refs['bpmn-canvas'],
        keyboard: { bindTo: document },
        additionalModules: this.addModules,
        moddleExtensions: this.addExtensions
      })
      this.bpmn.modeler = this.bpmnModeler
      this.$emit('init-finished', this.bpmnModeler)
      this.initEvent()
    },
    initEvent() {
      const eventBus = this.bpmnModeler.get('eventBus')
      const $this = this
      // 注册需要的监听事件, 将. 替换为 - , 避免解析异常
      this.events.forEach(event => {
        eventBus.on(event, function(eventObj) {
          const eventName = event.replace(/\./g, '-')
          const element = eventObj ? eventObj.element : null
          $this.$emit(eventName, element, eventObj)
        })
      })
      // 监听图形改变返回xml
      eventBus.on('commandStack.changed', async event => {
        try {
          this.recoverable = this.bpmnModeler.get('commandStack').canRedo()
          this.revocable = this.bpmnModeler.get('commandStack').canUndo()
          const { xml } = await this.bpmnModeler.saveXML({ format: true })
          this.$emit('commandStack-changed', event)
          this.$emit('input', xml)
          this.$emit('change', xml)
        } catch (e) {
          console.error(`[流程设计错误]: ${e.message || e}`)
        }
      })
      // 监听视图缩放变化
      this.bpmnModeler.on('canvas.viewbox.changed', ({ viewbox }) => {
        this.$emit('canvas-viewbox-changed', { viewbox })
        const { scale } = viewbox
        this.defaultZoom = Math.floor(scale * 100) / 100
      })
    },
    async createDiagram(xml, processDefId, processDefName) {
      // 将字符串转换成图显示出来
      const newId = processDefId || `Process_${new Date().getTime()}`
      const newName = processDefName || `业务流程_${new Date().getTime()}`
      const xmlString = xml || DefaultEmptyXML('Process_' + newId, newName, 'camunda')
      try {
        await this.bpmnModeler.importXML(xmlString)
      } catch (e) {
        console.error(`[流程设计错误]: ${e.message || e}`)
      }
    },
    // 预览
    previewProcessXML() {
      this.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
        this.previewData = xml
        this.previewVisible = true
      })
    },
    // 排列
    elementsAlign(align) {
      const Align = this.bpmnModeler.get('alignElements')
      const Selection = this.bpmnModeler.get('selection')
      const SelectedElements = Selection.get()
      if (!SelectedElements || SelectedElements.length <= 1) {
        this.$message.warning('请按住 Ctrl 键选择多个元素对齐')
        return
      }
      this.$confirm('自动对齐可能造成图形变形，是否继续？', '警告', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => Align.trigger(SelectedElements, align))
    },
    // 缩放
    processZoomIn(zoomStep = 0.1) {
      const newZoom = Math.floor(this.defaultZoom * 100 + zoomStep * 100) / 100
      if (newZoom > 4) {
        throw new Error('[Process Designer Warn ]: The zoom ratio cannot be greater than 4')
      }
      this.defaultZoom = newZoom
      this.bpmnModeler.get('canvas').zoom(this.defaultZoom)
    },
    processZoomOut(zoomStep = 0.1) {
      const newZoom = Math.floor(this.defaultZoom * 100 - zoomStep * 100) / 100
      if (newZoom < 0.2) {
        throw new Error('[Process Designer Warn ]: The zoom ratio cannot be less than 0.2')
      }
      this.defaultZoom = newZoom
      this.bpmnModeler.get('canvas').zoom(this.defaultZoom)
    },
    processReZoom() {
      this.defaultZoom = 1
      this.bpmnModeler.get('canvas').zoom('fit-viewport', 'auto')
    },
    processRedo() {
      this.bpmnModeler.get('commandStack').redo()
    },
    processUndo() {
      this.bpmnModeler.get('commandStack').undo()
    },
    processRestart() {
      this.recoverable = false
      this.revocable = false
      this.createDiagram(null).then(() => this.bpmnModeler.get('canvas').zoom(1, 'auto'))
    },
    /*loadProcess() {
      const { processDefId } = this.$route.query
      if (this.$Util.isEmpty(processDefId) || "0" === processDefId) {
        this.createDiagram(this.value)
        return
      }
      this.$get('process-service/process/loadDesign/' + processDefId)
        .then(res => {
          this.$resultMsg(this, res, true)
          if (res.code === 200 && res.data) {
            this.process = res.data
            this.createDiagram(this.process.processContent, processDefId).then(() => this.processReZoom())
          } else {
            this.createDiagram(this.value)
            this.$resultMsg(this, { msg: '流程定义不存在' })
          }
        })
    },*/
    saveProcess() {
      this.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
        this.saveXml(xml)
      })
    }
  }
}
</script>

<style scoped>

</style>
